package cn.liwuwa.handwritingprocessing;


import android.annotation.SuppressLint;

import org.opencv.android.OpenCVLoader;

import androidx.annotation.NonNull;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.photo.Photo;

import java.util.ArrayList;
import java.util.List;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;

/**
 * HandwritingprocessingPlugin
 */
public class HandwritingprocessingPlugin implements FlutterPlugin, MethodCallHandler {
    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private MethodChannel channel;

    private static boolean OpenCVFLag = false;

    private static final int BLACKWRITEVAL_OTSU = 1000;
    private static final double THREAD_MAXVALUE = 255;
    private static final int MAT_COLS_MUL = 20;
    private int blackWriteVal;
    private int removalRedVal;
    private int removalBlueVal;
    //修补大小
    private int patchSize;

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "handwritingprocessing");
        channel.setMethodCallHandler(this);
    }

    // This static function is optional and equivalent to onAttachedToEngine. It supports the old
    // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
    // plugin registration via this function while apps migrate to use the new Android APIs
    // post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
    //
    // It is encouraged to share logic between onAttachedToEngine and registerWith to keep
    // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
    // depending on the user's project. onAttachedToEngine or registerWith must both be defined
    // in the same class.
    public static void registerWith(Registrar registrar) {
        final MethodChannel channel = new MethodChannel(registrar.messenger(), "handwritingprocessing");
        channel.setMethodCallHandler(new HandwritingprocessingPlugin());
    }

    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        if (!OpenCVFLag) {
            if (!OpenCVLoader.initDebug()) {
                System.out.println("Err");
            } else {
                OpenCVFLag = true;
            }
            // Handle initialization error
        }
        if (call.method.equals("version")) {
            result.success("opencv " + Core.VERSION);
        } else if (call.method.equals("blackWrite")) {
            result.success(blackWrite((byte[]) call.argument("byteData")));
        } else if (call.method.equals("handwriting")) {
            blackWriteVal = (int) call.argument("blackWriteVal");
            removalRedVal = (int) call.argument("removalRedVal");
            removalBlueVal = (int) call.argument("removalBlueVal");
            patchSize = (int) call.argument("patchSize");
            result.success(handwriting((byte[]) call.argument("byteData"), (int) call.argument("type")));
        } else if (call.method.equals("inRange")) {
            result.success(inRange((byte[]) call.argument("byteData"), (double) call.argument("minh"), (double) call.argument("mins"), (double) call.argument("minv"), (double) call.argument("maxh"), (double) call.argument("maxs"), (double) call.argument("maxv")));
        } else {
            result.notImplemented();
        }
    }


    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);
    }

    /**
     * 区域范围
     *
     * @param byteData
     * @return
     */
    @SuppressLint("MissingPermission")
    public byte[] inRange(byte[] byteData, double minh, double mins, double minv, double maxh, double maxs, double maxv) {
        byte[] byteArray = new byte[0];
        try {

            Mat dst = new Mat();

            Mat dstOut = new Mat();
            // Decode image from input byte array
            Mat src = Imgcodecs.imdecode(new MatOfByte(byteData), Imgcodecs.IMREAD_UNCHANGED);

            Mat hsv = new Mat();
            // Convert the image to Gray
            Imgproc.cvtColor(src, hsv, Imgproc.COLOR_BGR2HSV);
            Mat bgr = new Mat();

            // inRange operation
            Core.inRange(hsv, new Scalar(minh, mins, minv), new Scalar(maxh, maxs, maxv), dst);

//            Mat lower_red_hue_range = new Mat();
//            Mat upper_red_hue_range = new Mat();
//
//            Core.inRange(hsv, new Scalar(100, 100, 100), new Scalar(124, 255, 255), lower_red_hue_range);
//            Core.inRange(hsv, new Scalar(100, 43, 46), new Scalar(124, 255, 255), upper_red_hue_range);
            Imgproc.morphologyEx(dst, dst, Imgproc.MORPH_DILATE, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3), new Point(-1, -1)));

//            Core.bitwise_not(dst, dstOut);
            Photo.inpaint(bgr, dst, dstOut, 1, Photo.INPAINT_NS);
            Core.bitwise_or(hsv, dst, dstOut);
        } catch (Exception e) {
            System.out.println("OpenCV Error: " + e.toString());
        }
        return byteArray;
    }


    /**
     * 黑白处理
     *
     * @param byteData
     * @return
     */
    @SuppressLint("MissingPermission")
    public byte[] blackWrite(byte[] byteData) {
        byte[] byteArray = new byte[0];
        try {
            Mat dst = new Mat();
            // Decode image from input byte array
            Mat src = Imgcodecs.imdecode(new MatOfByte(byteData), Imgcodecs.IMREAD_UNCHANGED);

            exThreshold(src, dst);

            //instantiating an empty MatOfByte class
            MatOfByte matOfByte = new MatOfByte();
            //Converting the Mat object to MatOfByte
            Imgcodecs.imencode(".jpg", dst, matOfByte);
            byteArray = matOfByte.toArray();
        } catch (Exception e) {
            System.out.println("OpenCV Error: " + e.toString());
        }
        return byteArray;
    }

    private void exThreshold(Mat src, Mat dst) {
        // Convert the image to Gray
        Mat srcGray = new Mat();
        Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_BGR2GRAY);

        // Adaptive Thresholding
        if (blackWriteVal == BLACKWRITEVAL_OTSU) {
            double maxValue = THREAD_MAXVALUE;
            Imgproc.threshold(srcGray, dst, 0, maxValue, Imgproc.THRESH_BINARY + Imgproc.THRESH_OTSU);
        } else {
            double maxValue = THREAD_MAXVALUE;
            int adaptiveMethod = Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C;
            int thresholdType = Imgproc.THRESH_BINARY;
            int cols = srcGray.cols() / MAT_COLS_MUL;
            int blockSize = cols % 2 == 0 ? cols + 1 : cols;
            double constantValue = blackWriteVal;
            Imgproc.adaptiveThreshold(srcGray, dst, maxValue, adaptiveMethod, thresholdType, blockSize, constantValue);
        }
    }

    /**
     * 字迹处理
     *
     * @param byteData
     * @return
     */
    @SuppressLint("MissingPermission")
    public byte[] handwriting(byte[] byteData, int type) {
        byte[] byteArray = new byte[0];
        boolean isBlackWrite = (type & 1) == 1;
        try {
            if (type == 1) {
                byteArray = blackWrite(byteData);
            }
            if ((type>>1 & 3) == 1) {
                byteArray = removalRed(byteData, isBlackWrite);
            }
            if ((type>>1 & 3) == 2) {
                byteArray = removalBlue(byteData, isBlackWrite);
            }
            if ((type>>1 & 3) == 3) {
                byteArray = removalRedAndBlue(byteData, isBlackWrite);
            }
        } catch (Exception e) {
            System.out.println("OpenCV Error: " + e.toString());
        }
        return byteArray;
    }

    /**
     * 去除红色笔迹
     *
     * @param byteData
     * @return
     */
    @SuppressLint("MissingPermission")
    public byte[] removalRed(byte[] byteData, boolean isBlackWrite) {
        byte[] byteArray = new byte[0];
        try {
            Mat dst = new Mat();

            Mat dstOut = new Mat();
            // Decode image from input byte array
            Mat src = Imgcodecs.imdecode(new MatOfByte(byteData), Imgcodecs.IMREAD_UNCHANGED);

            Mat bgr = new Mat();

            //android上的是RGBA，所以先转成BGR
            Imgproc.cvtColor(src, bgr, Imgproc.COLOR_RGBA2BGR);

            List<Mat> channes = new ArrayList<>();
            Core.split(bgr, channes);
            Mat matOne = channes.get(0);
            Mat matTwo = channes.get(1);
            Mat matThree = channes.get(2);
            Mat matSubOne = new Mat();
            Mat matSubTwo = new Mat();
            Core.subtract(matOne, matTwo, matSubOne);
            Core.subtract(matOne, matThree, matSubTwo);
            //sub color
            Imgproc.threshold(matSubOne, matSubOne, removalRedVal, 255, 0);
            Imgproc.threshold(matSubTwo, matSubTwo, removalRedVal, 255, 0);
            Core.bitwise_and(matSubOne, matSubTwo, dst);

            outMat(isBlackWrite, true,dst, dstOut, src, bgr);

            MatOfByte matOfByte = new MatOfByte();
            //Converting the Mat object to MatOfByte
            Imgcodecs.imencode(".jpg", dstOut, matOfByte);
            byteArray = matOfByte.toArray();
        } catch (Exception e) {
            System.out.println("OpenCV Error: " + e.toString());
        }
        return byteArray;
    }

    private void outMat(boolean isBlackWrite,boolean isWriteBg, Mat dst, Mat dstOut, Mat src, Mat bgr) {
        if (isBlackWrite) {
            exThreshold(src, dstOut);
            exMorphologyEx(dst, dstOut);
            Core.bitwise_or(dst, dstOut, dstOut);
        } else {
            if(isWriteBg) {
                Imgproc.cvtColor(dst, dst, Imgproc.COLOR_GRAY2BGR);
                Core.bitwise_or(dst, bgr, dstOut);
            }else {
                Photo.inpaint(bgr, dst, dstOut, 0, Photo.INPAINT_TELEA);
            }

            Imgproc.cvtColor(dstOut, dstOut, Imgproc.COLOR_BGR2RGBA);
        }
    }

    /**
     * 去除蓝色笔迹
     *
     * @param byteData
     * @return
     */
    @SuppressLint("MissingPermission")
    public byte[] removalBlue(byte[] byteData, boolean isBlackWrite) {
        byte[] byteArray = new byte[0];
        try {
            Mat dst = new Mat();

            Mat dstOut = new Mat();
            // Decode image from input byte array
            Mat src = Imgcodecs.imdecode(new MatOfByte(byteData), Imgcodecs.IMREAD_UNCHANGED);

            Mat bgr = new Mat();

            //android上的是RGBA，所以先转成BGR
            Imgproc.cvtColor(src, bgr, Imgproc.COLOR_RGBA2BGR);

            List<Mat> channes = new ArrayList<>();
            Core.split(bgr, channes);
            Mat blue = channes.get(0);
            Mat green = channes.get(1);
            Mat red = channes.get(2);
            Mat blueSub = new Mat();
            Mat greenSub = new Mat();
            Core.subtract(red, blue, blueSub);
            Core.subtract(red, green, greenSub);
//            Imgproc.blur(blueSub, blueSub, new Size(3, 3));
//            Imgproc.blur(greenSub, greenSub, new Size(3, 3));
//            Imgproc.medianBlur(blueSub, blueSub, 5);
//            Imgproc.medianBlur(greenSub, greenSub,5);
            Imgproc.threshold(blueSub, blueSub, removalBlueVal, 255, 0);
            Imgproc.threshold(greenSub, greenSub, removalBlueVal, 255, 0);
            Core.bitwise_and(blueSub, greenSub, dst);
//            Imgproc.morphologyEx(dst, dst, Imgproc.MORPH_DILATE, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3), new Point(-1, -1)));

//            Core.split(hsv, channes);
//            Core.inRange(hsv, new Scalar(0, 0, 0), new Scalar(180, 15, 200), dst);

//            Photo.inpaint(bgr, dst, dstOut, 3, Photo.INPAINT_NS);
//            Imgproc.cvtColor(dstOut, dstOut, Imgproc.COLOR_BGR2RGBA);

            outMat(isBlackWrite,true, dst, dstOut, src, bgr);

//            // inRange operation
//            Core.inRange(hsv, new Scalar(minh, mins, minv), new Scalar(maxh, maxs, maxv), dst);
//
////            Mat lower_red_hue_range = new Mat();
////            Mat upper_red_hue_range = new Mat();
////
////            Core.inRange(hsv, new Scalar(100, 100, 100), new Scalar(124, 255, 255), lower_red_hue_range);
////            Core.inRange(hsv, new Scalar(100, 43, 46), new Scalar(124, 255, 255), upper_red_hue_range);
//            Imgproc.morphologyEx(dst, dst, Imgproc.MORPH_DILATE, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3), new Point(-1, -1)));
//            dstOut = dst;
////            Core.bitwise_not(dst, dstOut);
//            Photo.inpaint(bgr, dst, dstOut, 1, Photo.INPAINT_NS);
//            Core.bitwise_or(hsv,dst,dstOut);

//            Imgproc.cvtColor(dstOut, dstOut, Imgproc.COLOR_BGR2RGBA);
            //instantiating an empty MatOfByte class
            MatOfByte matOfByte = new MatOfByte();
            //Converting the Mat object to MatOfByte
            Imgcodecs.imencode(".jpg", dstOut, matOfByte);
            byteArray = matOfByte.toArray();
        } catch (Exception e) {
            System.out.println("OpenCV Error: " + e.toString());
        }
        return byteArray;
    }

    private void exMorphologyEx(Mat dst, Mat dstOut) {
        if (patchSize > 0) {
            Mat dstOutTemp = new Mat();
            Core.bitwise_or(dst, dstOut, dstOutTemp);

            Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(patchSize, patchSize));
            Imgproc.morphologyEx(dstOutTemp, dstOutTemp, Imgproc.MORPH_RECT, element);//腐蚀操作

            Core.bitwise_and(dst, dstOutTemp, dst);
        }
    }


    /**
     * 去除蓝色和红色笔迹
     *
     * @param byteData
     * @return
     */
    @SuppressLint("MissingPermission")
    public byte[] removalRedAndBlue(byte[] byteData, boolean isBlackWrite) {
        byte[] byteArray = new byte[0];
        try {
            Mat dst = new Mat();

            Mat dstOut = new Mat();
            // Decode image from input byte array
            Mat src = Imgcodecs.imdecode(new MatOfByte(byteData), Imgcodecs.IMREAD_UNCHANGED);

            Mat bgr = new Mat();

            //android上的是RGBA，所以先转成BGR
            Imgproc.cvtColor(src, bgr, Imgproc.COLOR_RGBA2BGR);

            List<Mat> channes = new ArrayList<>();
            Core.split(bgr, channes);
            Mat blue = channes.get(0);
            Mat green = channes.get(1);
            Mat red = channes.get(2);
            Mat blueSub = new Mat();
            Mat greenSub = new Mat();
            Core.subtract(red, blue, blueSub);
            Core.subtract(red, green, greenSub);
//            Imgproc.blur(blueSub, blueSub, new Size(3, 3));
//            Imgproc.blur(greenSub, greenSub, new Size(3, 3));
//            Imgproc.medianBlur(blueSub, blueSub, 5);
//            Imgproc.medianBlur(greenSub, greenSub,5);
            Imgproc.threshold(blueSub, blueSub, removalBlueVal, 255, 0);
            Imgproc.threshold(greenSub, greenSub, removalBlueVal, 255, 0);
            Core.bitwise_and(blueSub, greenSub, dst);

//            Core.split(hsv, channes);
//            Core.inRange(hsv, new Scalar(0, 0, 0), new Scalar(180, 15, 200), dst);

// red
            Mat dstRed = new Mat();
            Mat matSubOne = new Mat();
            Mat matSubTwo = new Mat();
            Core.subtract(blue, green, matSubOne);
            Core.subtract(blue, red, matSubTwo);
            //sub color
            Imgproc.threshold(matSubOne, matSubOne, removalRedVal, 255, 0);
            Imgproc.threshold(matSubTwo, matSubTwo, removalRedVal, 255, 0);
            Core.bitwise_and(matSubOne, matSubTwo, dstRed);
            Core.bitwise_or(dstRed, dst, dst);

            outMat(isBlackWrite,true, dst, dstOut, src, bgr);

//            // inRange operation
//            Core.inRange(hsv, new Scalar(minh, mins, minv), new Scalar(maxh, maxs, maxv), dst);
//
////            Mat lower_red_hue_range = new Mat();
////            Mat upper_red_hue_range = new Mat();
////
////            Core.inRange(hsv, new Scalar(100, 100, 100), new Scalar(124, 255, 255), lower_red_hue_range);
////            Core.inRange(hsv, new Scalar(100, 43, 46), new Scalar(124, 255, 255), upper_red_hue_range);
//            Imgproc.morphologyEx(dst, dst, Imgproc.MORPH_DILATE, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3), new Point(-1, -1)));
//            dstOut = dst;
////            Core.bitwise_not(dst, dstOut);
//            Photo.inpaint(bgr, dst, dstOut, 1, Photo.INPAINT_NS);
//            Core.bitwise_or(hsv,dst,dstOut);

//            Imgproc.cvtColor(dstOut, dstOut, Imgproc.COLOR_BGR2RGBA);
            //instantiating an empty MatOfByte class
            MatOfByte matOfByte = new MatOfByte();
            //Converting the Mat object to MatOfByte
            Imgcodecs.imencode(".jpg", dstOut, matOfByte);
            byteArray = matOfByte.toArray();
        } catch (Exception e) {
            System.out.println("OpenCV Error: " + e.toString());
        }
        return byteArray;
    }

}
